package cn.infrabase.core.codec;

import cn.infrabase.core.util.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * <pre>
 * Base16（Hex）编码解码器。
 * 十六进制（简写为hex或下标16）在数学中是一种逢16进1的进位制，一般用数字0到9和字母A到F表示（其中:A~F即10~15）。
 * 例如：十进制数57，在二进制写作111001，在16进制写作39。
 * </pre>
 *
 * @author Weimin Gao
 * @since 1.0.0
 **/
public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequence, byte[]> {

    /**
     * Base16对象（使用小写字母字符表，如：0123456789abcdef）。
     */
    public static final Base16Codec CODEC_LOWER = new Base16Codec(true);

    /**
     * Base16对象（使用大写字母字符表，如：0123456789ABCDEF）。
     */
    public static final Base16Codec CODEC_UPPER = new Base16Codec(false);

    /**
     * Base16对象字符表，如：0123456789abcdef或0123456789ABCDEF。
     */
    private final char[] alphabets;

    /**
     * 构造函数。
     *
     * @param lower 设置真表示使用小写字母字符表。
     */
    public Base16Codec(boolean lower) {
        this.alphabets = (lower ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray();
    }

    /**
     * 编码给定的数据。
     *
     * @param data 给定的数据（待编码的数据）。
     * @return 编码后的数量。
     * @throws EncodeException 如果给定的数据编码发生错误时。
     */
    @Override
    public char[] encode(@Nonnull byte[] data) throws EncodeException {
        final int len = data.length;
        final char[] out = new char[len << 1];
        for (int i = 0, j = 0; i < len; i++) {
            // 高位
            out[j++] = alphabets[(0xF0 & data[i]) >>> 4];
            // 低位
            out[j++] = alphabets[0x0F & data[i]];
        }
        return out;
    }

    /**
     * <pre>
     * 解码给定的数据。
     * 说明：
     * 1、如果给定的数据为空{@link StringUtils#isEmpty}时，则返回null值。
     * </pre>
     *
     * @param encoded 给定的数据（待解码的数据）。
     * @return 解码后的数据。
     * @throws DecodeException 如果给定的数据解码发生错误时。
     */
    @Override
    public byte[] decode(@Nullable CharSequence encoded) throws DecodeException {
        if (StringUtils.isEmpty(encoded)) {
            return null;
        }
        byte one = 0x01;
        encoded = StringUtils.cleanBlank(encoded);
        int len = encoded.length();
        if ((len & one) != 0) {
            // 如果提供的数据是奇数长度，则前面补0凑偶数
            encoded = "0" + encoded;
            len = encoded.length();
        }
        final byte[] out = new byte[len >> 1];
        for (int i = 0, j = 0; j < len; i++) {
            char ch = encoded.charAt(j);
            int digit = Character.digit(ch, 16);
            if (digit < 0) {
                throw new DecodeException("待解码索引位置【" + j + "】数据【" + ch + "】为非法十六进制字符");
            }
            int f = digit(encoded.charAt(j), j) << 4;
            j++;
            f = f | digit(encoded.charAt(j), j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }
        return out;
    }

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 返回字符对应的十六进制整数。
     *
     * @param ch    字符。
     * @param index 字符的索引位置。
     * @return 字符对应的十六进制整数。
     */
    private int digit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit < 0) {
            throw new DecodeException("待解码索引位置【" + index + "】数据【" + ch + "】为非法十六进制字符");
        }
        return digit;
    }
}
